cdef extern from "tao.h" nogil:

    ctypedef char* TaoSolverType "const char*"
    TaoSolverType TAO_LMVM       '"tao_lmvm"'
    TaoSolverType TAO_NLS        '"tao_nls"'
    TaoSolverType TAO_NTR        '"tao_ntr"'
    TaoSolverType TAO_NTL        '"tao_ntl"'
    TaoSolverType TAO_CG         '"tao_cg"'
    TaoSolverType TAO_TRON       '"tao_tron"'
    TaoSolverType TAO_BLMVM      '"tao_blmvm"'
    TaoSolverType TAO_BQPIP      '"tao_bqpip"'
    TaoSolverType TAO_GPCG       '"tao_gpcg"'
    TaoSolverType TAO_NM         '"tao_nm"'
    TaoSolverType TAO_POUNDERS   '"tao_pounders"'
    TaoSolverType TAO_LCL        '"tao_lcl"'
    TaoSolverType TAO_SSILS      '"tao_ssils"'
    TaoSolverType TAO_SSFLS      '"tao_ssfls"'
    TaoSolverType TAO_ASILS      '"tao_asils"'
    TaoSolverType TAO_ASFLS      '"tao_asfls"'
    TaoSolverType TAO_FD_TEST    '"tao_fd_test"'

    ctypedef enum TaoSolverTerminationReason:
        #iterating
        TAO_CONTINUE_ITERATING
        # converged
        TAO_CONVERGED_FATOL
        TAO_CONVERGED_FRTOL
        TAO_CONVERGED_GATOL
        TAO_CONVERGED_GRTOL
        TAO_CONVERGED_GTTOL
        TAO_CONVERGED_STEPTOL
        TAO_CONVERGED_MINF
        TAO_CONVERGED_USER
        # diverged
        TAO_DIVERGED_MAXITS
        TAO_DIVERGED_NAN
        TAO_DIVERGED_MAXFCN
        TAO_DIVERGED_LS_FAILURE
        TAO_DIVERGED_TR_REDUCTION
        TAO_DIVERGED_USER


    int TaoView(TaoSolver,PetscViewer)
    int TaoDestroy(TaoSolver*)
    int TaoCreate(MPI_Comm,TaoSolver*)
    int TaoSetOptionsPrefix(TaoSolver, char[])
    int TaoGetOptionsPrefix(TaoSolver, char*[])
    int TaoSetFromOptions(TaoSolver)
    int TaoSetType(TaoSolver,TaoSolverType)
    int TaoGetType(TaoSolver,TaoSolverType*)

    int TaoSetUp(TaoSolver)
    int TaoSolve(TaoSolver)

    int TaoSetTolerances(TaoSolver,PetscReal,PetscReal,PetscReal,PetscReal,PetscReal)
    int TaoGetTolerances(TaoSolver,PetscReal*,PetscReal*,PetscReal*,PetscReal*,PetscReal*)
    int TaoSetConstraintTolerances(TaoSolver,PetscReal,PetscReal)
    int TaoGetConstraintTolerances(TaoSolver,PetscReal*,PetscReal*)

    int TaoSetFunctionLowerBound(TaoSolver,PetscReal)
    int TaoSetMaximumIterates(TaoSolver, PetscInt)
    int TaoSetMaximumFunctionEvaluations(TaoSolver, PetscInt)

    int TaoSetTrustRegionTolerance(TaoSolver,PetscReal)
    int TaoGetInitialTrustRegionRadius(TaoSolver,PetscReal*)
    int TaoGetTrustRegionRadius(TaoSolver,PetscReal*)
    int TaoSetTrustRegionRadius(TaoSolver,PetscReal)

    ctypedef int TaoConvergenceTest(TaoSolver,void*) except PETSC_ERR_PYTHON
    int TaoDefaultConvergenceTest(TaoSolver tao,void *dummy) except PETSC_ERR_PYTHON
    int TaoSetConvergenceTest(TaoSolver, TaoConvergenceTest*, void*)
    int TaoSetTerminationReason(TaoSolver,TaoSolverTerminationReason)
    int TaoGetTerminationReason(TaoSolver,TaoSolverTerminationReason*)
    int TaoGetSolutionStatus(TaoSolver,PetscInt*,
                             PetscReal*,PetscReal*,
                             PetscReal*,PetscReal*,
                             TaoSolverTerminationReason*)

    ctypedef int TaoMonitor(TaoSolver,void*) except PETSC_ERR_PYTHON
    ctypedef int (*TaoMonitorDestroy)(void**)
    int TaoSetMonitor(TaoSolver,TaoMonitor,void*,TaoMonitorDestroy)
    int TaoCancelMonitors(TaoSolver)





    int TaoComputeObjective(TaoSolver,PetscVec,PetscReal*)
    int TaoComputeSeparableObjective(TaoSolver,PetscVec,PetscVec)
    int TaoComputeGradient(TaoSolver,PetscVec,PetscVec)
    int TaoComputeObjectiveAndGradient(TaoSolver,PetscVec,PetscReal*,PetscVec)
    int TaoComputeConstraints(TaoSolver,PetscVec,PetscVec)
    int TaoComputeDualVariables(TaoSolver,PetscVec,PetscVec)
    int TaoComputeVariableBounds(TaoSolver)
    int TaoComputeHessian (TaoSolver,PetscVec,PetscMat*,PetscMat*,PetscMatStructure*)
    int TaoComputeJacobian(TaoSolver,PetscVec,PetscMat*,PetscMat*,PetscMatStructure*)

    int TaoSetInitialVector(TaoSolver,PetscVec)
    int TaoSetConstraintsVec(TaoSolver,PetscVec)
    int TaoSetVariableBounds(TaoSolver,PetscVec,PetscVec)
    int TaoSetHessianMat(TaoSolver,PetscMat,PetscMat)
    int TaoSetJacobianMat(TaoSolver,PetscMat,PetscMat)

    int TaoGetSolutionVector(TaoSolver,PetscVec*)
    int TaoGetGradientVector(TaoSolver,PetscVec*)
    int TaoGetVariableBounds(TaoSolver,PetscVec*,PetscVec*)
    #int TaoGetConstraintsVec(TaoSolver,PetscVec*)
    #int TaoGetVariableBoundVecs(TaoSolver,PetscVec*,PetscVec*)
    #int TaoGetHessianMat(TaoSolver,PetscMat*,PetscMat*)
    #int TaoGetJacobianMat(TaoSolver,PetscMat*,PetscMat*)

    ctypedef int TaoObjective(TaoSolver,PetscVec,PetscReal*,void*) except PETSC_ERR_PYTHON
    ctypedef int TaoSeparableObjective(TaoSolver,PetscVec,PetscVec,void*) except PETSC_ERR_PYTHON
    ctypedef int TaoGradient(TaoSolver,PetscVec,PetscVec,void*) except PETSC_ERR_PYTHON
    ctypedef int TaoObjGrad(TaoSolver,PetscVec,PetscReal*,PetscVec,void*) except PETSC_ERR_PYTHON
    ctypedef int TaoVarBounds(TaoSolver,PetscVec,PetscVec,void*) except PETSC_ERR_PYTHON
    ctypedef int TaoConstraints(TaoSolver,PetscVec,PetscVec,void*) except PETSC_ERR_PYTHON
    ctypedef int TaoHessian(TaoSolver,PetscVec,
                            PetscMat*,PetscMat*,PetscMatStructure*,
                            void*) except PETSC_ERR_PYTHON
    ctypedef int TaoJacobian(TaoSolver,PetscVec,
                             PetscMat*,PetscMat*,PetscMatStructure*,
                             void*) except PETSC_ERR_PYTHON
    ctypedef int TaoJacobianState(TaoSolver,PetscVec,
                                  PetscMat*,PetscMat*,PetscMat*,PetscMatStructure*,
                                  void*) except PETSC_ERR_PYTHON
    ctypedef int TaoJacobianDesign(TaoSolver,PetscVec,PetscMat*,
                                   void*) except PETSC_ERR_PYTHON

    int TaoSetObjectiveRoutine(TaoSolver,TaoObjective*,void*)
    int TaoSetSeparableObjectiveRoutine(TaoSolver,PetscVec,TaoSeparableObjective,void*)
    int TaoSetGradientRoutine(TaoSolver,TaoGradient*,void*)
    int TaoSetObjectiveAndGradientRoutine(TaoSolver,TaoObjGrad*,void*)
    int TaoSetVariableBoundsRoutine(TaoSolver,TaoVarBounds*,void*)
    int TaoSetConstraintsRoutine(TaoSolver,PetscVec,TaoConstraints*,void*)
    int TaoSetHessianRoutine(TaoSolver,PetscMat,PetscMat,TaoHessian*,void*)
    int TaoSetJacobianRoutine(TaoSolver,PetscMat,PetscMat,TaoJacobian*,void*)

    int TaoSetStateDesignIS(TaoSolver,PetscIS,PetscIS)
    int TaoSetJacobianStateRoutine(TaoSolver,PetscMat,PetscMat,PetscMat,TaoJacobianState*,void*)
    int TaoSetJacobianDesignRoutine(TaoSolver,PetscMat,TaoJacobianDesign*,void*)

    int TaoGetKSP(TaoSolver,PetscKSP*)

# --------------------------------------------------------------------

cdef inline Solver new_Solver(TaoSolver slv):
    cdef Solver ob = <Solver> Solver()
    ob.slv = slv
    PetscINCREF(ob.obj)
    return ob

# --------------------------------------------------------------------

cdef int Tao_Objective(TaoSolver _slv,
                       PetscVec _x, PetscReal *_f,
                       void *_ctx) except PETSC_ERR_PYTHON with gil:

    cdef Solver slv = new_Solver(_slv)
    cdef Vec    x   = new_Vec(_x)
    (objective, args, kargs) = slv.get_attr("__objective__")
    retv = objective(slv, x, *args, **kargs)
    _f[0] = retv
    return 0

cdef int Tao_SeparableObjective(TaoSolver _slv,
                                PetscVec _x, PetscVec _f,
                                void *_ctx) except PETSC_ERR_PYTHON with gil:

    cdef Solver slv = new_Solver(_slv)
    cdef Vec    x   = new_Vec(_x)
    cdef Vec    f   = new_Vec(_f)
    (separable, args, kargs) = slv.get_attr("__separable__")
    separable(slv, x, f, *args, **kargs)
    return 0

cdef int Tao_Gradient(TaoSolver _slv,
                      PetscVec _x, PetscVec _g,
                      void *_ctx) except PETSC_ERR_PYTHON with gil:

    cdef Solver slv = new_Solver(_slv)
    cdef Vec    x   = new_Vec(_x)
    cdef Vec    g   = new_Vec(_g)
    (gradient, args, kargs) = slv.get_attr("__gradient__")
    gradient(slv, x, g, *args, **kargs)
    return 0


cdef int Tao_ObjGrad(TaoSolver _slv,
                     PetscVec _x, PetscReal *_f, PetscVec _g,
                     void *_ctx) except PETSC_ERR_PYTHON with gil:

    cdef Solver slv = new_Solver(_slv)
    cdef Vec    x   = new_Vec(_x)
    cdef Vec    g   = new_Vec(_g)
    (objgrad, args, kargs) = slv.get_attr("__objgrad__")
    retv = objgrad(slv, x, g, *args, **kargs)
    _f[0] = retv
    return 0

cdef int Tao_Constraints(TaoSolver _slv,
                         PetscVec _x, PetscVec _r,
                         void *_ctx) except PETSC_ERR_PYTHON with gil:

    cdef Solver slv = new_Solver(_slv)
    cdef Vec    x   = new_Vec(_x)
    cdef Vec    r   = new_Vec(_r)
    (constraints, args, kargs) = slv.get_attr("__constraints__")
    constraints(slv, x, r, *args, **kargs)
    return 0

cdef int Tao_VarBounds(TaoSolver _slv,
                       PetscVec _xl, PetscVec _xu,
                       void *_ctx) except PETSC_ERR_PYTHON with gil:

    cdef Solver slv = new_Solver(_slv)
    cdef Vec    xl  = new_Vec(_xl)
    cdef Vec    xu  = new_Vec(_xu)
    (varbounds, args, kargs) = slv.get_attr("__varbounds__")
    varbounds(slv, xl, xu, *args, **kargs)
    return 0

cdef int Tao_Hessian(TaoSolver _slv,
                     PetscVec  _x,
                     PetscMat  *_H,
                     PetscMat  *_P,
                     PetscMatStructure* _s,
                     void* ctx) except PETSC_ERR_PYTHON with gil:
    cdef Solver slv = new_Solver(_slv)
    cdef Vec    x   = new_Vec(_x)
    cdef Mat    H   = new_Mat(_H[0])
    cdef Mat    P   = new_Mat(_P[0])
    (hessian, args, kargs) = slv.get_attr("__hessian__")
    retv = hessian(slv, x, H, P, *args, **kargs)
    _s[0] = matstructure(retv)
    cdef PetscMat Htmp = NULL, Ptmp = NULL
    Htmp = _H[0]; _H[0] = H.mat; H.mat = Htmp
    Ptmp = _P[0]; _P[0] = P.mat; P.mat = Ptmp
    return 0

cdef int Tao_Jacobian(TaoSolver _slv,
                      PetscVec  _x,
                      PetscMat  *_J,
                      PetscMat  *_P,
                      PetscMatStructure* _s,
                      void* ctx) except PETSC_ERR_PYTHON with gil:
    cdef Solver slv = new_Solver(_slv)
    cdef Vec    x   = new_Vec(_x)
    cdef Mat    J   = new_Mat(_J[0])
    cdef Mat    P   = new_Mat(_P[0])
    (jacobian, args, kargs) = slv.get_attr("__jacobian__")
    retv = jacobian(slv, x, J, P, *args, **kargs)
    _s[0] = matstructure(retv)
    cdef PetscMat Jtmp = NULL, Ptmp = NULL
    Jtmp = _J[0]; _J[0] = J.mat; J.mat = Jtmp
    Ptmp = _P[0]; _P[0] = P.mat; P.mat = Ptmp
    return 0

cdef int Tao_JacobianState(TaoSolver _slv,
                           PetscVec  _x,
                           PetscMat  *_J,
                           PetscMat  *_P,
                           PetscMat  *_I,
                           PetscMatStructure* _s,
                           void* ctx) except PETSC_ERR_PYTHON with gil:
    cdef Solver slv = new_Solver(_slv)
    cdef Vec    x   = new_Vec(_x)
    cdef Mat    J   = new_Mat(_J[0])
    cdef Mat    P   = new_Mat(_P[0])
    cdef Mat    I   = new_Mat(_I[0])
    (jacobian, args, kargs) = slv.get_attr("__jacobian_state__")
    retv = jacobian(slv, x, J, P, I, *args, **kargs)
    _s[0] = matstructure(retv)
    cdef PetscMat Jtmp = NULL, Ptmp = NULL, Itmp = NULL
    Jtmp = _J[0]; _J[0] = J.mat; J.mat = Jtmp
    Ptmp = _P[0]; _P[0] = P.mat; P.mat = Ptmp
    Itmp = _I[0]; _I[0] = I.mat; I.mat = Itmp
    return 0

cdef int Tao_JacobianDesign(TaoSolver _slv,
                            PetscVec  _x,
                            PetscMat  *_J,
                            void* ctx) except PETSC_ERR_PYTHON with gil:
    cdef Solver slv = new_Solver(_slv)
    cdef Vec    x   = new_Vec(_x)
    cdef Mat    J   = new_Mat(_J[0])
    (jacobian, args, kargs) = slv.get_attr("__jacobian_design__")
    retv = jacobian(slv, x, J, *args, **kargs)
    cdef PetscMat Jtmp = NULL
    Jtmp = _J[0]; _J[0] = J.mat; J.mat = Jtmp
    return 0

cdef int Tao_Converged(TaoSolver  slv,
                       void* ctx) except PETSC_ERR_PYTHON with gil:
    # call first the default convergence test
    CHKERR( TaoDefaultConvergenceTest(slv, NULL) )
    # call next the user-provided convergence test
    cdef Solver solver = new_Solver(slv)
    (converged, args, kargs) = solver.get_attr('__converged__')
    reason = converged(solver, *args, **kargs)
    if reason is None:  return 0
    # handle value of convergence reason
    cdef TaoSolverTerminationReason creason = TAO_CONTINUE_ITERATING
    if reason is False or reason == -1:
        creason = TAO_DIVERGED_USER
    elif reason is True or reason == 1:
        creason = TAO_CONVERGED_USER
    else:
        creason = reason
        assert creason >= TAO_DIVERGED_USER
        assert creason <= TAO_CONVERGED_USER
    CHKERR( TaoSetTerminationReason(slv, creason) )
    return 0

cdef int Tao_Monitor(TaoSolver slv,
                     void* ctx) except PETSC_ERR_PYTHON with gil:
    cdef Solver solver = new_Solver(slv)
    cdef object monitorlist = solver.get_attr('__monitor__')
    if monitorlist is None: return 0
    for (monitor, args, kargs) in monitorlist:
        monitor(solver, *args, **kargs)
    return 0

# --------------------------------------------------------------------
